package com.wex.app.tools;

import ohos.media.codec.Codec;
import ohos.media.common.BufferInfo;

import java.nio.ByteBuffer;
import java.util.LinkedList;

import static ohos.media.codec.Codec.createDecoder;
import static ohos.media.codec.Codec.createEncoder;

public class MediaCodec {

    // ---------- 数据结构 ----------

    static public class Packet {
        public byte[] lpData;
        public int dwSize;
        public int dwFlags;
        public int dwTimeStamp;
        public int dwWidth;
        public int dwHeight;
        public int dwFmt;
    }

    static public class Context {
        public int cbSize;         ///< 结构体大小
        public int dwCodecId;      ///< 编解码器ID
        public int dwFlags;        ///< 相关标志
        public byte[] szCodecName; ///< 编解码器的名称

        // 编解码器私有数据
        public Object lpPrivateData; ///< 私有数据指针

        // 音频部分参数
        public int dwChannels;      ///< 音频通道数
        public int dwSamplesPerSec; ///< 音频采样率
        public int dwBitsPerSample; ///< 音频量化位数
        public int dwFrameSize;     ///< 音频编码器处理帧长

        // 视频部分
        public int dwWidth;     ///< 视频宽度
        public int dwHeight;    ///< 视频高度
        public int dwFrameRate; ///< 帧率
        public int dwGopSize;   ///< 关键帧间隔
        public int PixFmt;      ///< 视频帧格式

        // 编码设置
        public int dwBitrate; ///< 目标码率，单位：bps
        public int dwQuality; ///< 质量
        public int dwPreset;  ///< 预设参数

        // 显示部分
        public Object lpGlobalContext; ///< 全局环境，Android中为JavaVM*
        public Object lpSuface;        ///< 显示表面句柄

        public int dwCodeWidth;       ///< 视频流对应的宽度
        public int dwCodeHeight;      ///< 视频流对应的高度
        public int dwChangeVideoSize; ///< 应用于解码输出分辩率被外部修改，通知解码器
        public int[] reserved;        ///< 保留
    }

    // ---------- encode三件套,成功返回0 ----------

    static public int InitEncoder(MediaCodec.Context lpContext) {
        lpContext.lpPrivateData = new MediaCodec.BackWorker(
                lpContext.dwWidth,
                lpContext.dwHeight,
                lpContext.dwFrameRate,
                lpContext.dwBitrate,
                true);
        return ((MediaCodec.BackWorker)lpContext.lpPrivateData).createCodec();
    }

    static public int Encode(MediaCodec.Context lpContext, MediaCodec.Packet InPacket, MediaCodec.Packet OutPacket) {
        return ((MediaCodec.BackWorker)lpContext.lpPrivateData).doCodec(InPacket, OutPacket);
    }

    static public int CloseEncoder(MediaCodec.Context lpContext) {
        ((MediaCodec.BackWorker)lpContext.lpPrivateData).releaseCodec();
        return 0;
    }

    // ---------- decode三件套,成功返回0 ----------

    static public int InitDecoder(MediaCodec.Context lpContext) {
        lpContext.lpPrivateData = new MediaCodec.BackWorker(
                lpContext.dwWidth,
                lpContext.dwHeight,
                lpContext.dwFrameRate,
                lpContext.dwBitrate,
                false);
        return ((MediaCodec.BackWorker)lpContext.lpPrivateData).createCodec();
    }

    static public int Decode(MediaCodec.Context lpContext, MediaCodec.Packet InPacket, MediaCodec.Packet OutPacket) {
        return ((MediaCodec.BackWorker)lpContext.lpPrivateData).doCodec(InPacket, OutPacket);
    }

    static public int CloseDecoder(MediaCodec.Context lpContext) {
        ((MediaCodec.BackWorker)lpContext.lpPrivateData).releaseCodec();
        return 0;
    }

    // ========== 私有代码段 ==========

    static private class BackWorker {

        public BackWorker(int width, int height, int fps, int bitRate, boolean isEnc) {
            //编解码格式配置
            format = new ohos.media.common.Format();
            format.putStringValue(ohos.media.common.Format.MIME, ohos.media.common.Format.VIDEO_AVC); // h264:VIDEO_AVC h265:VIDEO_HEVC
            format.putIntValue(ohos.media.common.Format.WIDTH, width);
            format.putIntValue(ohos.media.common.Format.HEIGHT, height);
            format.putIntValue(ohos.media.common.Format.FRAME_RATE, fps);
            format.putIntValue(ohos.media.common.Format.BITRATE_MODE, 1);
            format.putIntValue(ohos.media.common.Format.FRAME_INTERVAL, 1);
            if (isEnc) {
                format.putIntValue(ohos.media.common.Format.COLOR_MODEL, 21);
                format.putIntValue(ohos.media.common.Format.BIT_RATE, bitRate); // bitRate = 10000000
            }
            //其他参数
            isEncode = isEnc;
        }

        public int createCodec() {
            codec = isEncode ? createEncoder() : createDecoder();
            if (codec == null)
                return -1;

            if (!codec.registerCodecListener(listener)) {
                codec.release();
                codec = null;
                return -2;
            }

            if (!codec.setCodecFormat(format)) {
                codec.release();
                codec = null;
                return -3;
            }

            if (!codec.start()) {
                codec.release();
                codec = null;
                return -4;
            }

            fifo = new LinkedList();
            return 0;
        }

        public int doCodec(MediaCodec.Packet InPacket, MediaCodec.Packet OutPacket) {
            if (codec == null)
                return -1;

            //允许传入空包,此时跳过该步
            if (InPacket.dwSize > 0) {
                //阻塞
                ByteBuffer buff = codec.getAvailableBuffer(-1);
                if (buff != null) {
                    buff.clear();
                    //获取需求数据量信息
                    BufferInfo info = new BufferInfo();
                    info.bufferType = BufferInfo.BUFFER_TYPE_PARTIAL_FRAME;
                    info.offset = 0;
                    info.size = InPacket.dwSize;
                    info.timeStamp = InPacket.dwTimeStamp;
                    //往编/解码器推数据
                    if (info.size > 0) {
                        buff.put(InPacket.lpData);
                        codec.writeBuffer(buff, info);
                    }
                } else
                    return -1;
            }

            //缓冲区提取数据(LinkedList非线程安全,需加锁)
            synchronized (BackWorker.this) {
                if (fifo.isEmpty())
                    OutPacket.dwSize = 0;
                else {
                    OutPacket.lpData = (byte[]) fifo.getFirst();
                    fifo.removeFirst();
                    if (OutPacket.lpData != null)
                        OutPacket.dwSize = OutPacket.lpData.length;
                }
            }
            return 0;
        }

        public int releaseCodec() {
            if (codec == null)
                return -1;
            codec.stop();
            codec.release();
            codec = null;
            fifo.clear();
            fifo = null;
            return 0;
        }

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            if (codec != null)
                releaseCodec();
        }

        private Codec codec = null;
        private ohos.media.common.Format format;
        private LinkedList fifo;
        private boolean isEncode;

        //编解码器数据就绪监听
        private Codec.ICodecListener listener = new Codec.ICodecListener() {
            @Override
            public void onReadBuffer(ByteBuffer byteBuffer, BufferInfo bufferInfo, int i) {
                if (bufferInfo.size > 0) {
                    byte[] bytes = new byte[bufferInfo.size];
                    byteBuffer.get(bytes, bufferInfo.offset, bufferInfo.size);
                    //LinkedList非线程安全,需加锁
                    synchronized (BackWorker.this) {
                        fifo.add(bytes);
                    }
                }
            }
            @Override
            public void onError(int i, int i1, int i2) {}
        };
    }

}
